{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# lab geometry functions\n",
    "\n",
    "the FullControl lab exists for things that aren't suitable for the main FullControl package yet, potentially due to complexity in terms of their concept, code, hardware requirements, computational requirements, etc.\n",
    " \n",
    "FullControl features/functions/classes in the lab may be more experimental in nature and should be used with caution, with an understanding that they may change in future updates\n",
    "\n",
    "at present, both the lab and the regular FullControl packages are under active development and the code and package structures may change considerably. some aspects currently in FullControl may move to lab and vice versa\n",
    "\n",
    "lab currently has the following aspects:\n",
    "- geometry functions that supplement existing geometry functions in FullControl\n",
    "- four-axis and five-axis demos\n",
    "- transformation of a fullcontrol ***design*** into a 3D model (stl file) of the designed geometry with extudate heights and widths based on the designed `ExtrusionGeometry` \n",
    "\n",
    "this notebook briefly demonstrates the geometry functions. four-axis, five-axis and 3d-model-output capabilities are demonstrated in their respective noetbooks:\n",
    "- [5-axis notebook](lab_five_axis_demo.ipynb)\n",
    "- [4-axis notebook](lab_four_axis_demo.ipynb)\n",
    "- [stl-output notebook](lab_stl_output.ipynb)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### FullControl lab import"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import fullcontrol as fc\n",
    "import lab.fullcontrol as fclab\n",
    "from math import tau, radians"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### bezier curves"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "bez_points = []\n",
    "bez_points.append(fc.Point(x=10, y=10, z=0))\n",
    "bez_points.append(fc.Point(x=10, y=0, z=0))\n",
    "bez_points.append(fc.Point(x=0, y=10, z=0))\n",
    "bez_points.append(fc.Point(x=10, y=10, z=0))\n",
    "bez_points.append(fc.Point(x=9, y=9, z=2))\n",
    "steps = fclab.bezier(bez_points, num_points=100)\n",
    "fc.transform(steps, 'plot', fc.PlotControls(style=\"line\", zoom=0.8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### polar sine waves"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "centre = fc.Point(x=0, y=0, z=0)\n",
    "radius = 10\n",
    "amplitude = -2\n",
    "start_angle = 0\n",
    "arc_angle = radians(270)\n",
    "periods = 12\n",
    "segments_per_period = 8\n",
    "extra_half_period = False\n",
    "phase_shift = 0\n",
    "steps = fclab.arc_sinewaveXY(centre, radius, amplitude, start_angle, arc_angle, periods, segments_per_period, extra_half_period, phase_shift)\n",
    "fc.transform(steps, 'plot', fc.PlotControls(style=\"line\", zoom=0.8, color_type='print_sequence'))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### convex (streamline slicing)\n",
    "\n",
    "the CONVEX (CONtinuously Varied EXtrusion) approach allows continuously varying extrusion width. i.e. streamline-slicing\n",
    "\n",
    "see method images and case study in the associated journal paper [(free download)](https://www.researchgate.net/publication/346098541)\n",
    "\n",
    "two outer edges are defined as paths and the CONVEX function fills the space between these edges with the desired number of paths\n",
    "\n",
    "to travel between the end of one paths and start of the next, set `travel=True`\n",
    "\n",
    "it optionally allows speed to be continuously matched to instantaneous extrusion width to maintain constant volumetric flowrate:\n",
    "- set `vary_speed` parameter to be True and supply values for `speed_ref` and `width_ref` parameters\n",
    "- these parameters are used to change speed proportional to how wide the instantaneous segment being printed is compared to width_ref\n",
    "\n",
    "for open paths, it's useful to print lines using a zigzag strategy to avoid the nozzle moving back to the same side after printing each line - this is achieved by setting `zigzag=True`\n",
    "\n",
    "set `overextrusion_percent` to achieve more extrusion while not changing the physical separation of line - this can help ensure good lateral bonding between neighbouring lines \n",
    "\n",
    "it can be used to fill an arbirary shape by setting the 'inner edge' to be a list of coincident points near the centre of the shape. or for shapes with longish aspect ratios, 'inner edge' could be an abritrary list of points going along the appoximate medial axis and back.\n",
    "\n",
    "these are example implementations, CONVEX can be used far more broadly"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "outline_edge_1 = fc.circleXY(fc.Point(x=0, y=0, z=0.2), 5, 0, 64)\n",
    "outline_edge_2 = fc.circleXY(fc.Point(x=1, y=0, z=0.2), 3, 0, 64)\n",
    "steps = fclab.convex_pathsXY(outline_edge_1, outline_edge_2, 10)\n",
    "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence', style='tube'))\n",
    "\n",
    "# to vary speed to maintain constant flow rate:\n",
    "# steps = fclab.convex_pathsXY(outline_edge_1, outline_edge_2, 10, vary_speed = True, speed_ref = 1000, width_ref = 0.6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "outline_edge_1 = fclab.bezier([fc.Point(x=0, y=0, z=0.2), fc.Point(x=10, y=5, z=0.2), fc.Point(x=20, y=0, z=0.2)], num_points = 100)\n",
    "outline_edge_2 = fclab.bezier([fc.Point(x=0, y=10, z=0.2), fc.Point(x=10, y=5, z=0.2), fc.Point(x=20, y=10, z=0.2)], num_points = 100)\n",
    "steps = fclab.convex_pathsXY(outline_edge_1, outline_edge_2, 15, zigzag=True, travel=False)\n",
    "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence', style='tube'))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "instead of printing the above example geometry with lines running from end to end, you could design an imaginary 'outline' running along the medial axis of the specimen\n",
    "\n",
    "then the part can be printed it in a similar manner to the ring example above, with lines printed from the outside inwards - there's just no hole in the middle\n",
    "\n",
    "this is similar to concentric print paths in slicers, except the lines continuously fluctuate in width to achieve a shape-fitting path, which avoids islands needing to be printed with travel moves in between"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "points = 100\n",
    "outline_edge_1 = fclab.bezier([fc.Point(x=0, y=0, z=0.2), fc.Point(x=10, y=5, z=0.2), fc.Point(x=20, y=0, z=0.2)], num_points = points)\n",
    "outline_edge_2_reverse = fclab.bezier([fc.Point(x=20, y=10, z=0.2), fc.Point(x=10, y=5, z=0.2), fc.Point(x=0, y=10, z=0.2)], num_points = points)\n",
    "inner_edge_1 = fc.segmented_line(fc.Point(x=4, y=5, z=0.2), fc.Point(x=16, y=5, z=0.2), points)\n",
    "inner_edge_2_reverse = fc.segmented_line(fc.Point(x=16, y=5, z=0.2), fc.Point(x=4, y=5, z=0.2), points)\n",
    "# create a closed path of the outline\n",
    "outer_edge = outline_edge_1 + outline_edge_2_reverse + [outline_edge_1[0]]\n",
    "# create a 'path' along the medial axis with the same number of points as 'outer_edge'\n",
    "inner_edge = inner_edge_1 + inner_edge_2_reverse + [inner_edge_1[0]]\n",
    "steps = fclab.convex_pathsXY(outer_edge, inner_edge, 7, travel=False)\n",
    "steps.append(fc.PlotAnnotation(point = fc.Point(x=10, y=5, z=5), label=\"the default tube plotting style may not represent sudden changes in widths accurately\"))\n",
    "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence', style='tube'))\n",
    "steps[-1] = (fc.PlotAnnotation(point = fc.Point(x=10, y=5, z=5), label=\"use fc.PlotControls(tube_type='cylinders') to get more accurate representations of printed widths\"))\n",
    "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence', style='tube', tube_type='cylinders'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "for arbitrary geometry with width-to-length aspect ratios approximately <2.5, it may be feasible to set the inner 'path' to be a list of identical points at a chosen centre point of the geometry\n",
    "\n",
    "this example shows how CONVEX can fluctuate speed automatically to maintain constant volumetric flow rate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "outer_edge = fclab.bezier([fc.Point(x=0, y=0, z=0), \n",
    "                           fc.Point(x=20, y=0, z=0), \n",
    "                           fc.Point(x=-10, y=6, z=0), \n",
    "                           fc.Point(x=20, y=12, z=0), \n",
    "                           fc.Point(x=0, y=12, z=0)], num_points=100)\n",
    "outer_edge = fc.move_polar(outer_edge, fc.Point(x=0, y=6), 0, tau/2, copy=True, copy_quantity=2)\n",
    "inner_edge = [fc.Point(x=0, y=6, z=0)]*len(outer_edge)\n",
    "steps = fclab.convex_pathsXY(outer_edge, inner_edge, 12, travel=True, vary_speed=True, speed_ref=2000, width_ref=0.5)\n",
    "widths_required = [step.width for step in steps if isinstance(step, fc.ExtrusionGeometry)]\n",
    "speeds_required = [step.print_speed for step in steps if isinstance(step, fc.Printer)]\n",
    "print(f'extrusion width varies from {min(widths_required):.2} to {max(widths_required):.2} mm')\n",
    "print(f'speed varies from {int(min(speeds_required))} to {int(max(speeds_required))} mm/min, to maintain constant volumetric flow rate')\n",
    "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence', style='tube', tube_type='flow'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### solid base\n",
    "\n",
    "automatically add solid layers to an existing design for a shell structure with fill_base_simple() or fill_base_full(), which use the convex function\n",
    "\n",
    "the layer outline is determined based on the 'segments_per_layer' parameter\n",
    "\n",
    "fill_base_simple generates solid fill for the first layer then copies it for subsequent solid layers\n",
    "fill_base_full generates solid fill for each layer based on the current layer's outline, which is more computationally demanding, but useful if the model does now have near-vertical walls\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "segments_per_layer = 64\n",
    "helix_layers = 20\n",
    "helix_start_rad, helix_end_rad = 5, 5\n",
    "centre = fc.Point(x=0, y=0, z=0)\n",
    "helix = fc.helixZ(centre, helix_start_rad, helix_end_rad, 0, helix_layers, 0.2, helix_layers*segments_per_layer)\n",
    "\n",
    "solid_layers, EW = 5, 0.5\n",
    "steps = fclab.fill_base_simple(helix, segments_per_layer, solid_layers, EW)\n",
    "fc.transform(steps, 'plot', fc.PlotControls(style=\"line\", zoom=0.8))\n",
    "helix_end_rad = 10\n",
    "helix = fc.helixZ(centre, helix_start_rad, helix_end_rad, 0, helix_layers, 0.2, helix_layers*segments_per_layer)\n",
    "steps = fclab.fill_base_simple(helix, segments_per_layer, solid_layers, EW)\n",
    "fc.transform(steps, 'plot', fc.PlotControls(style=\"line\", zoom=0.8))\n",
    "steps = fclab.fill_base_full(helix, segments_per_layer, solid_layers, EW)\n",
    "fc.transform(steps, 'plot', fc.PlotControls(style=\"line\", zoom=0.8))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### offset a path\n",
    "\n",
    "required parameters:\n",
    "\n",
    "- points: the supplied path - it must be a list of Point objects only\n",
    "- offset: the distance to offset the path\n",
    "\n",
    "optional parameters:\n",
    "\n",
    "- flip: set True to flip the direction of the offset\n",
    "- travel: set True to travel to the offset path without printing\n",
    "- repeats: set the number of offsets paths - default = 1\n",
    "- include_original: set True to return the original path as well as offset paths\n",
    "- arc_outer_corners: set True to make outer corners have arcs (good for acute corners)\n",
    "- arc_segments: numbers of segments par arc (if arc_outer_corners == True) - default = 8"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "points = [fc.Point(x=10, y=10, z=0.2), fc.Point(x=15, y=15, z=0.2), fc.Point(x=20, y=10, z=0.2)]\n",
    "offset = 0.4\n",
    "steps = fclab.offset_path(points, offset, include_original=True, travel=True)\n",
    "steps.insert(-2, fc.PlotAnnotation(label=\"the 'travel' parameter enables travel movements to the start of offset paths\"))\n",
    "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence', zoom=0.7, tube_type='cylinders'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "points = [fc.Point(x=10, y=10, z=0.2), fc.Point(x=15, y=15, z=0.2), fc.Point(x=20, y=10, z=0.2), fc.Point(x=10, y=10, z=0.2)]\n",
    "offset = 0.4\n",
    "steps = fclab.offset_path(points, offset, include_original=True, travel=True)\n",
    "steps.append(fc.PlotAnnotation(label=\"the offset path for a closed path is automatically closed too\"))\n",
    "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence', zoom=0.8, tube_type='cylinders'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "points = [fc.Point(x=10, y=10, z=0.2), fc.Point(x=15, y=15, z=0.2), fc.Point(x=20, y=10, z=0.2), fc.Point(x=10, y=10, z=0.2)]\n",
    "offset = 0.4\n",
    "steps = fclab.offset_path(points, offset, travel=True, repeats=3, include_original=True)\n",
    "steps.insert(-2, fc.PlotAnnotation(label=\"path repeated multiple times using the 'repeat' parameters\"))\n",
    "points2 = fc.move(points, fc.Vector(y=-7.5))\n",
    "steps.extend(fc.travel_to(fc.Point(x=10, y=2.5)))\n",
    "steps.extend(fclab.offset_path(points2, offset, travel=True, repeats=3, include_original=True, flip=True))\n",
    "steps.insert(-2, fc.PlotAnnotation(label=\"path offset direction flipped using the 'flip' parameter\"))\n",
    "fc.transform(steps, 'plot',fc.PlotControls(color_type='print_sequence', zoom=0.8, tube_type='cylinders'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "points = [fc.Point(x=10, y=10, z=0.2), fc.Point(x=15, y=15, z=0.2), fc.Point(x=20, y=10, z=0.2), fc.Point(x=10, y=10, z=0.2)]\n",
    "offset = 0.4\n",
    "steps = fclab.offset_path(points, offset, repeats=10, travel = True, include_original=True, arc_outer_corners=True, arc_segments=16)\n",
    "steps.append(fc.PlotAnnotation(label=\"add radii to corners with 'arc_outer_corners' and 'arc_segments' parameters\"))\n",
    "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence', tube_type='flow'))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### reflect a list of points\n",
    "\n",
    "reflecting a list of points is complicated by the fact that the order in which controls are applied (e.g. to turn extrusion on or off) needs careful consideration - see more details about this in the regular [geometry functions notebook](geometry_functions.ipynb)\n",
    "\n",
    "the following command can be used to reflect a list of points if it only contains points"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "steps = [fc.Point(x=0, y=0, z=0), fc.Point(x=1, y=1, z=0)]\n",
    "steps += fclab.reflectXYpolar_list(steps, fc.Point(x=2, y=0, z=0), tau/4)\n",
    "for step in steps: print(step)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### find line intersection\n",
    "\n",
    "methods to find the intersection or to check for intersection between lines are demonstrated in the following code cell"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "line1 = [fc.Point(x=0, y=0, z=0), fc.Point(x=1, y=1, z=0)]\n",
    "line2 = [fc.Point(x=1, y=0, z=0), fc.Point(x=0, y=1, z=0)]\n",
    "intersection_point = fclab.line_intersection_by_points_XY(line1[0], line1[1], line2[0], line2[1])\n",
    "print(f'\\ntest 1... intersection at Point: {intersection_point}')\n",
    "\n",
    "line_1_point = fc.Point(x=0, y=1, z=0)\n",
    "line_1_angle = 0\n",
    "line_2_point = fc.Point(x=1, y=0, z=0)\n",
    "line_2_angle = tau/4\n",
    "intersection_point = fclab.line_intersection_by_polar_XY(line_1_point, line_1_angle, line_2_point, line_2_angle)\n",
    "print(f'\\ntest 2... intersection at Point: {intersection_point}')\n",
    "\n",
    "line1 = [fc.Point(x=0, y=0, z=0), fc.Point(x=1, y=1, z=0)]\n",
    "line2 = [fc.Point(x=1, y=0, z=0), fc.Point(x=0, y=1, z=0)]\n",
    "intersection_check = fclab.crossing_lines_check_XY(line1[0], line1[1], line2[0], line2[1])\n",
    "print(f'\\ntest 3... intersection between lines (within their length): {intersection_check}')"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### loop between lines\n",
    "\n",
    "'loop_between_lines' allows smooth continuous printing between two lines - particularly useful for printing sacrificial material outside the region of interest for tissue engineering lattices, etc."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "line1 = [fc.Point(x=0, y=0, z=0.2), fc.Point(x=0, y=10, z=0.2)]\n",
    "line2 = [fc.Point(x=10, y=10, z=0.2), fc.Point(x=20, y=0, z=0.2)]\n",
    "loop_extension = 10 # dictates how far the loop extends past the lines\n",
    "loop_linearity = 0 # 0 to 10 - disctates how linearly the loop initially extends beyond the desired lines\n",
    "loop = fclab.loop_between_lines(line1[0], line1[1], line2[0], line2[1], loop_extension, travel=True, num_points=20, linearity=loop_linearity)\n",
    "steps = line1 + loop + line2\n",
    "fc.transform(steps, 'plot')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### spherical coordinates\n",
    "\n",
    "spherical coordinates can be be used to define points with the fclab.spherical_to_point function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "point = fclab.spherical_to_point(origin = fc.Point(x=100, y=0, z=0), radius = 1, angle_xy=radians(90), angle_z = radians(0))\n",
    "print(f'fclab.spherical_to_point() for angle_xy=90 degrees, angle_z = 0 degrees: \\n    {repr(point)}')\n",
    "point = fclab.spherical_to_point(origin = fc.Point(x=100, y=0, z=0), radius = 1, angle_xy=radians(90), angle_z = radians(45))\n",
    "print(f'fclab.spherical_to_point() for angle_xy=90 degrees, angle_z = 45 degrees: \\n    {repr(point)}')\n",
    "point = fclab.spherical_to_point(origin = fc.Point(x=100, y=0, z=0), radius = 1, angle_xy=radians(90), angle_z = radians(90))\n",
    "print(f'fclab.spherical_to_point() for angle_xy=90 degrees, angle_z = 90 degrees: \\n    {repr(point)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "fclab.spherical_to_vector is similar to fclab.spherical_to_point but does not need an origin to be defined since vectors can be considered to always be relative to xyz=0\n",
    "\n",
    "the 'radius' parameter has also been replaced by the more logical term 'length' "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "point = fclab.spherical_to_vector(length=10, angle_xy=radians(90), angle_z=radians(45))\n",
    "print(f'fclab.spherical_to_vector() for angle_xy=90 degrees, angle_z = 45 degrees: \\n    {repr(point)}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "fullcontrol also has functions to determine spherical angles and radius from two points using fclab.point_to_spherical()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "point1 = fc.Point(x=10, y=0, z=0)\n",
    "point2 = fc.Point(x=10, y=0, z=10)\n",
    "spherical_data = fclab.point_to_spherical(origin_point=point1, target_point=point2)\n",
    "print(f'fclab.point_to_spherical() returns:\\n    {repr(spherical_data)}')\n",
    "angleZ_data = fclab.angleZ(start_point=point1, end_point=point2)\n",
    "print(f'fclab.angleZ() returns:\\n    {repr(angleZ_data)}')\n",
    "\n",
    "recreated_point2 = fclab.spherical_to_point(point1,spherical_data.radius, spherical_data.angle_xy, spherical_data.angle_z)\n",
    "print(f'recreated point 2 using spherical data:\\n    {repr(point2)}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3D rotation\n",
    "\n",
    "rotate the toolpath or sections of the toolpath in 3D with fclab.rotate()\n",
    "\n",
    "the function requires:\n",
    "- list of points\n",
    "- start point for the axis or rotation\n",
    "- end point for the axis of rotation (or 'x', 'y', or 'z')\n",
    "- angle of rotation\n",
    "- similar to fc.move(), if multiple copies are required:\n",
    "    - copy = True\n",
    "    - copy_quantity = number desired (including original)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "steps = fc.circleXY(fc.Point(x=10,y=0,z=0), 5,0)\n",
    "steps = fclab.rotate(steps,fc.Point(x=30,y=0,z=0), 'y', tau/200, copy=True, copy_quantity=75)\n",
    "fc.transform(steps, 'plot', fc.PlotControls(zoom=0.7))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "start_rad, end_rad, EH = 3, 1, 0.4\n",
    "\n",
    "bez_points = [fc.Point(x=0, y=0, z=0), fc.Point(x=0, y=0, z=10), fc.Point(x=10, y=0, z=10),\n",
    "              fc.Point(x=10, y=0, z=20), fc.Point(x=0, y=0, z=20), fc.Point(x=-10, y=0, z=20)]\n",
    "layers = int(fc.path_length(fclab.bezier(bez_points, 100))/EH)  # use 100 points to calculate bezier path length, then divide by extrusion height to get the number of layers\n",
    "centres = fclab.bezier(bez_points, layers)\n",
    "\n",
    "radii = fc.linspace(start_rad, end_rad, layers) \n",
    "segment_z_angles = [fclab.angleZ(point1, point2) if point2.x > point1.x else -fclab.angleZ(point1, point2)\n",
    "                    for point1, point2 in zip(centres[:-1], centres[1:])]\n",
    "angles = segment_z_angles + [segment_z_angles[-1]]  # last point has now segment after it, so use the angle of the previous segment\n",
    "\n",
    "steps = []\n",
    "for layer in range(layers):\n",
    "    circle = fc.circleXY(centres[layer], radii[layer], 0, 64)\n",
    "    circle = fclab.rotate(circle, centres[layer], 'y', angles[layer])\n",
    "    steps.extend(circle + [fc.PlotAnnotation(point=centres[layer], label='')])\n",
    "\n",
    "fc.transform(steps, 'plot', fc.PlotControls(style='line', zoom=0.4, color_type='print_sequence'))\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "fc",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.3"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
